/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2016-04-23
 */

#include <sfud.h>
#include <stdarg.h>

#include "drivers/ext/spi_dev.h"
#include "sys_common.h"
#include "sys_log.h"

void sfud_log_debug(const char *file, const long line, const char *format, ...);

/**
 * SPI write data then read data
 */
static sfud_err _port_spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
                                     size_t read_size)
{
    /**
     * add your spi write and read code
     */

    spi_dev_t spi_dev_hdl = spi->user_data;
    if (spi_dev_hdl == NULL)
    {
        return SFUD_ERR_READ;
    }

    if (spi_dev_lock(spi_dev_hdl) != 0)
    {
        return SFUD_ERR_TIMEOUT;
    }

    sfud_err result = SFUD_ERR_READ;
    do
    {
        if (spi_dev_cs_reset(spi_dev_hdl) != 0)
        {
            result = SFUD_ERR_READ;
            break;
        }

        if (write_buf && write_size)
        {
            if (spi_dev_write(spi_dev_hdl, write_buf, write_size) != 0)
            {
                result = SFUD_ERR_READ;
                break;
            }
        }

        if (read_buf && read_size)
        {
            if (spi_dev_read(spi_dev_hdl, read_buf, read_size) != 0)
            {
                result = SFUD_ERR_READ;
                break;
            }
        }

        result = SFUD_SUCCESS;

    } while (0);

    spi_dev_cs_set(spi_dev_hdl);
    spi_dev_unlock(spi_dev_hdl);

    return result;
}

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err _port_qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
                                uint8_t *read_buf, size_t read_size)
{
    /**
     * add your qspi read flash data code
     */

    spi_dev_t spi_dev_hdl = spi->user_data;
    if (spi_dev_hdl == NULL)
    {
        return SFUD_ERR_WRITE;
    }

    if (spi_dev_lock(spi_dev_hdl) != 0)
    {
        return SFUD_ERR_TIMEOUT;
    }

    sfud_err result = SFUD_ERR_WRITE;
    do
    {
        if (spi_dev_cs_reset(spi_dev_hdl) != 0)
        {
            result = SFUD_ERR_WRITE;
            break;
        }

        if (read_buf && read_size)
        {
            if (spi_dev_read(spi_dev_hdl, read_buf, read_size) != 0)
            {
                result = SFUD_ERR_WRITE;
                break;
            }
        }

        result = SFUD_SUCCESS;

    } while (0);

    spi_dev_cs_set(spi_dev_hdl);
    spi_dev_unlock(spi_dev_hdl);

    return result;
}
#endif /* SFUD_USING_QSPI */

static void _port_lock(const struct __sfud_spi *spi)
{
    spi_dev_t spi_dev_hdl = spi->user_data;
    if (spi_dev_hdl)
    {
        spi_dev_lock(spi_dev_hdl);
    }
}

static void _port_unlock(const struct __sfud_spi *spi)
{
    spi_dev_t spi_dev_hdl = spi->user_data;
    if (spi_dev_hdl)
    {
        spi_dev_unlock(spi_dev_hdl);
    }
}

static void _port_sleep(void)
{
    sys_delay(1);
}

sfud_err sfud_spi_port_init(sfud_flash *flash)
{
    sfud_err result = SFUD_ERR_NOT_FOUND;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = _port_spi_write_read; //Required
     *    flash->spi._port_qspi_read = _port_qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */

    if (flash->spi.user_data)
    {
        return SFUD_SUCCESS;
    }

    spi_dev_t dev = spi_dev_binding(flash->spi.name);
    if (dev == NULL)
    {
        return result;
    }

    if (spi_dev_init(dev, NULL) != 0)
    {
        return result;
    }

    flash->spi.wr = _port_spi_write_read;   // Required
    flash->spi.qspi_read = _port_qspi_read; // Required when QSPI mode enable
    flash->spi.lock = _port_lock;
    flash->spi.unlock = _port_unlock;
    flash->spi.user_data = (void *)dev;

    flash->retry.delay = _port_sleep; // Delay 1 mS
    flash->retry.times = 200;         // Required

    result = SFUD_SUCCESS;

    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    SYS_PRINT("[INF] | [SFUD] (%s:%ld) ", _FILENAME(file), line);
    SYS_VPRINT(format, args);
    SYS_PRINT("\r\n");
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    SYS_PRINT("[INF] | [SFUD] ");
    SYS_VPRINT(format, args);
    SYS_PRINT("\r\n");
    va_end(args);
}
